﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Linq;

namespace ListItemEditor.UI
{
    public partial class MultiSelect : UserControl
    {
        #region constructor

        public MultiSelect()
        {
            InitializeComponent();
        }

        #endregion

        #region variables

        private List<object> _selectedValues;
        private PropertyDescriptor _valueProperty;

        #endregion

        #region properties

        public string DisplayMember { get; set; }
        public string ValueMember { get; set; }

        public bool ChoicesSorted
        {
            get { return this.lbChoices.Sorted; }
            set { this.lbChoices.Sorted = value; }
        }

        public object Choices { get; set; }
        public List<object> SelectedValues
        {
            get
            {
                if (this.Choices == null || this.lbSelect.Items == null) return this._selectedValues;
                return this.lbSelect.Items.Cast<object>().Select(o => this.GetItemValue(o)).ToList();
            }
            set
            {
                this._selectedValues = value;
            }
        }

        #endregion

        #region public methods

        public void DataBind()
        {
            this._valueProperty = null;
            if (this.Choices == null)
            {
                this.lbChoices.DataSource = null;
                this.lbSelect.DataSource = null;
            }
            else
            {
                if (!(this.Choices is IList) && !(this.Choices is IListSource))
                    throw new ApplicationException("Choices property should be either IList or IListSource.");

                IList choicesList = this.Choices as IList;
                if (choicesList == null) choicesList = ((IListSource)this.Choices).GetList();

                this.lbChoices.DisplayMember = this.lbSelect.DisplayMember = this.DisplayMember;
                this.lbChoices.ValueMember = this.lbSelect.ValueMember = this.ValueMember;

                if (this._selectedValues == null) this._selectedValues = new List<object>();
                HashSet<object> set = new HashSet<object>(this._selectedValues.Cast<object>());
                Dictionary<object, object> map = new Dictionary<object, object>();
                foreach (object item in choicesList)
                {
                    object value = this.GetItemValue(item);
                    if (set.Contains(value)) map[value] = item;
                    else this.lbChoices.Items.Add(item);
                }
                foreach (object value in this._selectedValues)
                {
                    if (map.ContainsKey(value)) this.lbSelect.Items.Add(map[value]);
                }
            }
        }

        #endregion

        #region private methods

        private object GetItemValue(object item)
        {
            if (this.ValueMember == null) return item;
            if (this._valueProperty == null)
            {
                PropertyDescriptorCollection properties = ListBindingHelper.GetListItemProperties(this.Choices);
                this._valueProperty = properties[this.ValueMember];
            }
            return this._valueProperty.GetValue(item);
        }

        private void MoveSelectedItems(ListBox src, ListBox dest, bool keepSelection)
        {
            List<int> sel = src.SelectedIndices.Cast<int>().ToList();
            sel.Sort();
            List<int> newInidces = sel.Select(id => dest.Items.Add(src.Items[id])).ToList();

            if (keepSelection)
            {
                dest.SelectedIndices.Clear();
                foreach(int id in newInidces) dest.SelectedIndices.Add(id);
            }

            sel.Reverse();
            foreach (int id in sel) src.Items.RemoveAt(id);
        }

        private void MoveAllItems(ListBox src, ListBox dest)
        {
            foreach (object item in src.Items) dest.Items.Add(item);
            src.Items.Clear();
        }

        private void ChangeSelectedItemsOrder(ListBox lb, int diff)
        {
            if (diff == 0 || lb.SelectedIndices.Count == 0) return;
            List<int> selIdx = lb.SelectedIndices.Cast<int>().ToList();
            selIdx.Sort();
            List<int> newSelIdx = new List<int>(selIdx.Count);

            if (diff > 0) selIdx.Reverse();
            int selCount = 0, itemCount = lb.Items.Count;
            foreach (int idx in selIdx)
            {
                int newIdx = idx + diff;
                if (diff > 0 && newIdx >= itemCount - selCount) newIdx = itemCount - selCount - 1;
                else if (diff < 0 && newIdx < selCount) newIdx = selCount;

                object item = lb.Items[idx];
                lb.Items.RemoveAt(idx);
                lb.Items.Insert(newIdx, item);
                
                newSelIdx.Add(newIdx);
                selCount++;
            }

            lb.SelectedIndices.Clear();
            foreach (int id in newSelIdx) lb.SelectedIndices.Add(id);
        }

        #endregion

        #region events

        private void btnRight_Click(object sender, EventArgs e)
        {
            MoveSelectedItems(this.lbChoices, this.lbSelect, true);
        }

        private void btnLeft_Click(object sender, EventArgs e)
        {
            MoveSelectedItems(this.lbSelect, this.lbChoices, false);
        }

        private void btnAllRight_Click(object sender, EventArgs e)
        {
            MoveAllItems(this.lbChoices, this.lbSelect);
        }

        private void btnAllLeft_Click(object sender, EventArgs e)
        {
            MoveAllItems(this.lbSelect, this.lbChoices);
        }

        private void btnUp_Click(object sender, EventArgs e)
        {
            ChangeSelectedItemsOrder(this.lbSelect, -1);
        }

        private void btnDown_Click(object sender, EventArgs e)
        {
            ChangeSelectedItemsOrder(this.lbSelect, 1);
        }

        private void btnTop_Click(object sender, EventArgs e)
        {
            if (this.lbSelect.Items == null) return;
            ChangeSelectedItemsOrder(this.lbSelect, -1 * this.lbSelect.Items.Count);
        }

        private void btnBottom_Click(object sender, EventArgs e)
        {
            if (this.lbSelect.Items == null) return;
            ChangeSelectedItemsOrder(this.lbSelect, this.lbSelect.Items.Count);
        }

        private void lbChoices_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                MoveSelectedItems(this.lbChoices, this.lbSelect, true);
            }
        }

        private void lbSelect_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                MoveSelectedItems(this.lbSelect, this.lbChoices, false);
            }
        }

        #endregion

    }
}
